<?php

declare(strict_types=1);

namespace App\CustHub\Service;

use App\Common\Constants\PayMethodConstants;
use App\Common\Constants\PayWayConstants;
use App\Common\Dto\PaymentDto;
use App\Common\Helpers\BcMath;
use App\Common\Helpers\UrlHelper;
use App\Common\Interfaces\PaymentInterface;
use App\Common\Robot\Robot;
use App\CustHub\Constants\OrderTypeConstants;
use App\CustHub\Constants\SeatOrderStatusConstants;
use App\CustHub\Constants\UserMoneyLogTypeConstants;
use App\CustHub\Interfaces\OpenWorkApplicationInterface;
use App\CustHub\Mapper\ChCorpMapper;
use App\CustHub\Mapper\ChOrderMapper;
use App\CustHub\Mapper\ChRobotMapper;
use App\CustHub\Mapper\ChSeatMapper;
use App\CustHub\Mapper\ChSeatOrderMapper;
use App\CustHub\Mapper\ChSeatPackageMapper;
use App\CustHub\Mapper\ChUserMapper;
use App\CustHub\Model\ChRobot;
use App\CustHub\Model\ChSeat;
use App\CustHub\Model\ChSeatPackage;
use App\CustHub\Vo\UserBalanceVo;
use App\Exception\BusinessException;
use App\Robot\Dto\CommonJobDto;
use App\Robot\Job\CommonJob;
use App\Setting\Service\SettingConfigService;
use Carbon\Carbon;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Tappable\HigherOrderTapProxy;
use Mine\Abstracts\AbstractService;
use Mine\Annotation\Transaction;
use Mine\Exception\NormalStatusException;
use Mine\Helper\Str;
use Mine\MineModel;
use function Hyperf\Collection\data_get;
use function Symfony\Component\String\u;

/**
 * 机器人席位
 */
class ChSeatService extends AbstractService
{
    public $mapper;

    #[Inject]
    protected ChServerService $serverService;

    #[Inject]
    protected ChSeatPackageMapper $packageMapper;

    #[Inject]
    protected ChUserMapper $userMapper;

    #[Inject]
    protected ChSeatOrderMapper $orderMapper;

    #[Inject]
    protected PaymentInterface $paymentService;

    #[Inject]
    protected ChCorpMapper $corpMapper;

    #[Inject]
    protected LicenseService $licenseService;

    #[Inject]
    protected OpenOrderService $openOrderService;

    #[Inject]
    protected ChRobotMapper $robotMapper;

    public function __construct(ChSeatMapper $mapper)
    {
        $this->mapper = $mapper;
    }

    public function list(array $params)
    {
        $pageSize = $params['page_size'] ?? MineModel::PAGE_SIZE;

        $keyword = data_get($params,'keyword','');
        $seat_type = data_get($params,'seat_type','');
        $type = data_get($params,'type','');
        $status = data_get($params,'status','');
        $robotId = data_get($params,'robot_id','');
        $useStatus = data_get($params,'use_status','');
        $expireStartTime  = data_get($params,'expire_start_time','');
        $expireEndTime  = data_get($params,'expire_start_time','');

        $paginate = $this->mapper->model::query()
            ->select(['id','corp_id','code','status','use_status','expire_time','type','is_auto_renewal','seat_type','updated_at'])
            ->with(['robot' => function ($query) {
                $query->select(['id','nickname','avatar','corp_name','seat_id']);
            },'corp'=>function ($query) {
                $query->select(['id','corp_name']);
            }])->orderBy('id','desc')
            ->where('uid',$params['uid'])
            ->when(filled($seat_type),function ($query) use ($seat_type){
                $query->where('seat_type',$seat_type);
            })
            ->when(filled($type),function ($query) use ($type){
                $query->where('type',$type);
            })
            ->when(filled($status),function ($query) use ($status){
                $query->where('status',$status);
            })
            ->when(!empty($keyword),function ($query) use ($keyword){
                $query->whereHas('robot',function ($query) use ($keyword
                ){
                   $query->where('nickname','like','%'.$keyword.'%');
                });
            })
            ->when(!empty($robotId),function ($query) use ($robotId){
                $query->whereHas('robot',function ($query) use ($robotId){
                    $query->where('id',$robotId);
                });
            })
            ->when(filled($useStatus),function ($query) use ($useStatus){
                $query->where('use_status',$useStatus);
            })
            ->when(!empty($expireStartTime),function ($query) use ($expireStartTime){
                $query->where('expire_time','>=',strtotime($expireStartTime));
            })
            ->when(!empty($expireEndTime),function ($query) use ($expireEndTime){
                $query->where('expire_time','<=',strtotime($expireEndTime));
            })
            ->paginate((int)$pageSize);

        $htSeatIds = $paginate->getCollection()->filter(function ($item) use ($params){
           return $item->seat_type == 1 && $item->use_status == 1;
        })->pluck('id')->toArray();
        $htRobotMap = [];
        if(!empty($htSeatIds)){
            $htRobotMap = ChRobot::query()
                ->select(['id','nickname','avatar','corp_name','seat_id','ht_seat_id'])
                ->where('main_uid',$params['main_uid'])
                ->whereIn('ht_seat_id',$htSeatIds)
                ->get()->keyBy('ht_seat_id')->toArray();
        }

        $paginate->getCollection()->transform(function ($item) use ($htRobotMap){
            $item['expire_time_at'] = Carbon::parse($item['expire_time'])->format('Y-m-d H:i:s');
            $item->corp_name = $item->corp->corp_name ?? '';
            if($item->seat_type == 1 && $item->use_status == 1){
                $item->ht_robot = $htRobotMap[$item->id] ?? null;
            }
            unset($item->corp);
            return $item;
        });

        return $this->mapper->setPaginate($paginate);
    }

    public function getSeatNum(array $params)
    {
        $seatList = $this->mapper->model::query()
            ->select(['id','status','use_status'])
            ->where('main_uid',$params['main_uid'])
            ->where('seat_type',0)
            ->get();

        $onlineNum = $seatList->filter(function ($item){
            return $item->use_status == 1;
        })->count();


        $yxNum = $seatList->filter(function ($item){
            return $item->status == 1;
        })->count();

        $kyNum = $yxNum - $onlineNum;

        return [
            'total_num' => $seatList->count(),
            'yx_num' => $yxNum,
            'online_num' => $onlineNum,
            'ky_num' => $kyNum
        ];
    }

    /**
     * 购买席位
     * @param array $params
     */
    public function buy(array $params)
    {
        $package = $this->packageMapper->findById($params['package_id']);
        if(empty($package)){
            throw new NormalStatusException("请选择套餐");
        }
        $corpId = $params['corp_id'] ?? 0;
//        if($package->seat_type == 1){
//            $corpInfo = $this->corpMapper->findById($corpId);
//            if(empty($corpInfo->auth_corp_id)){
//                throw new NormalStatusException("请先授权企业微信");
//            }
//
//            if($this->openOrderService->checkOrder($corpInfo->auth_corp_id)){
//                throw new NormalStatusException("一天内最多只能购买3次");
//            }
//        }

        $price = $package['price'];
        $num = $params['num'];

        $totalAmount = BcMath::mul($price,$num);
        $payAmount = $totalAmount;

        $orderData = [
            'uid' => $params['uid'],
            'order_no' => generateOrderNumber(),
            'total_amount' => $totalAmount,
            'pay_amount' => $payAmount,
            'business_id' => $package->id,
            'month' => $this->getMontyByType($package->type),
            'account_type' => $package->account_type,
            'seat_type' => $package->seat_type,
            'corp_id' => $corpId,
            'num' => $num,
            'remark' => '购买席位'
        ];

        $orderId = $this->orderMapper->save($orderData);
        if(!$orderId){
            throw new NormalStatusException('购买失败请重试');
        }

        container()->get(LowQueueService::class)->push(new CommonJob(new CommonJobDto(
            classname: ChSeatService::class,
            method: 'payTimeout',
            data:[
                'order_no' => $orderData['order_no']
            ]
        )),30 * 60);

        return $orderData['order_no'];
    }

    public function pay(array $data): array
    {
        $payWay = $data['pay_way'];
        $order = $this->orderMapper->findByOrderNo($data['order_no']);
        if(empty($order)){
            throw new NormalStatusException('请先创建订单');
        }

        $isUseBalance = data_get($data, 'is_use_balance', 0);
        $balance = 0;
        if ($isUseBalance) {
            //使用余额
            $userInfo = $this->userMapper->findById($data['uid']);
            $balance = $userInfo['money'];
        }

        $order->pay_way = $payWay;
        $order->is_use_balance = $isUseBalance;
        $order->balance = $balance;
        $rst = $order->save();
        if(!$rst){
            throw new NormalStatusException('支付失败，请重试');
        }

        $result = $this->toPay($order->uid,$order->order_no,$balance,$payWay);
        if($result === false){
            throw new NormalStatusException('支付失败，请重试');
        }
        return $result;
    }

    /**
     * 检测支付状态
     * @param array $params
     * @return HigherOrderTapProxy|int|mixed|null
     */
    public function checkPayStatus(array $params): mixed
    {
        $orderNo = $params['order_no'];
        $order = $this->orderMapper->findByOrderNo($orderNo);
        if(empty($order)){
            return 0;
        }
        return $order->status;
    }

    /**
     * 续费
     * @param array $params
     */
    public function renewal(array $params)
    {
        $package = $this->packageMapper->findById($params['package_id']);
        if(empty($package)){
            throw new NormalStatusException("请选择套餐");
        }

        $seatInfo = $this->mapper->findById($params['id']);
        if(empty($seatInfo)){
            throw new NormalStatusException("请选择席位");
        }

        $price = $package['price'];
        $num = 1;


        $totalAmount = BcMath::mul($price,(string)$num);
        $payAmount = $totalAmount;

        $orderData = [
            'uid' => $params['uid'],
            'order_no' => generateOrderNumber(),
            'total_amount' => $totalAmount,
            'pay_amount' => $payAmount,
            'business_id' => $seatInfo->id,
            'month' => $this->getMontyByType($package->type),
            'account_type' => $package->account_type,
            'seat_type' => $package->seat_type,
            'corp_id' => $seatInfo->corp_id,
            'num' => $num,
            'type' => 1,
            'remark' => '续费席位'
        ];

        $orderId = $this->orderMapper->save($orderData);
        if(!$orderId){
            throw new NormalStatusException('续费失败请重试');
        }

        container()->get(LowQueueService::class)->push(new CommonJob(new CommonJobDto(
            classname: ChSeatService::class,
            method: 'payTimeout',
            data:[
                'order_no' => $orderData['order_no']
            ]
        )),30 * 60);
        return $orderData['order_no'];
    }

    public function toPay(mixed $uid,string $orderNo,$balance,$payWay)
    {
        $order = $this->orderMapper->findByOrderNo($orderNo);

        $orderId = $order->id;

        $payAmount = $order->pay_amount;
        if($balance >= $payAmount){
            //余额足够
            $rst = container()->get(UserBalanceService::class)->updateBalance(new UserBalanceVo(
                uid: $uid,
                money: $payAmount,
                businessType: UserMoneyLogTypeConstants::BUY_SEAT,
                order_id: $orderId,
                remark: "购买席位"
            ));
            if(!$rst){
                return false;
            }

            if($order->type){
               $rst =  $this->renewalSeatNotify($orderNo,'','');
            }else{
                $rst = $this->buySeatNotify($orderNo,'','');
            }
            if(!$rst){
                return false;
            }
            return ['order_id' => $orderId];
        }else{
            $payAmount = BcMath::sub($payAmount,$balance);

            if($balance > 0){
                $rst = container()->get(UserBalanceService::class)->updateBalance(new UserBalanceVo(
                    uid: $uid,
                    money: $balance,
                    businessType: UserMoneyLogTypeConstants::BUY_SEAT,
                    order_id: $orderId,
                    remark: "购买席位-混合支付"
                ));
                if(!$rst){
                    return false;
                }
            }

            $payment = new PaymentDto();
            $payment->setOrderNo($orderNo);
            $payment->setPayAmount($payAmount);
            $payment->setPayWay($payWay);
            $systemConfig = container()->get(SettingConfigService::class);
            $domain = $systemConfig->getConfigByKey('ch_api_url')['value'];

            if($order->type){
                //续费
                $url = 'pay/notify/renewalSeatNotify';
            }else{
                //购买
                $url = 'pay/notify/buySeatNotify';
            }

            if($payWay == PayWayConstants::WECHAT){
                $notifyUrl = $domain.'/'.$url;
            }else{
                $notifyUrl = UrlHelper::url($url,['pay_way'=>$payWay],$domain);
            }

            $payment->setNotifyUrl($notifyUrl);
            $payment->setSubject('购买席位');
            $payment->setPayMethod(PayMethodConstants::SCAN);

            $rst = $this->paymentService->unifiedPay($payment);

            if(empty($rst)){
                return false;
            }
            return $rst;
        }
    }

    /**
     * 席位订单超时取消
     * @param array $data
     * @return void
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function payTimeout(array $data): void
    {
        $orderNo = $data['order_no'];
        $orderInfo = $this->orderMapper->findByOrderNo($orderNo);
        if(empty($orderInfo) || $orderInfo->status != 0){
            return;
        }
        $orderInfo->status = 2;
        $orderInfo->save();
        logger()->info('席位订单超时取消成功',$data);
    }

    /**
     * 购买席位回调
     * @param $outTradeNo
     * @param $transactionId
     * @param $ext
     * @return bool
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function buySeatNotify($outTradeNo,$transactionId,$ext):bool
    {
        $order = $this->orderMapper->findByOrderNo($outTradeNo);

        if(empty($order)){
            return false;
        }

        if($order->status != SeatOrderStatusConstants::UNPAID && $order->status != SeatOrderStatusConstants::CANCEL){
            return false;
        }

        Db::beginTransaction();
        try {
            if($order->seat_type == 1){
                //需要购买指定数量的互通账号接口许可证
                $rst = $this->licenseService->createOrder($order,$transactionId);
                if(!$rst){
                    throw new NormalStatusException("购买互通账号接口许可证失败");
                }
            }else{
                $order->status = 1;
                $order->transaction_id = $transactionId;
                $order->pay_time = Carbon::now()->toDateTimeString();
                $result = $order->save();
                if(!$result){
                    throw new NormalStatusException("订单编号:{$outTradeNo}_状态更新失败");
                }
                $rst = $this->addSeat($order->uid,$order->num,$order->account_type,$order->seat_type,$order->month);
                if(!$rst){
                    throw new NormalStatusException("订单编号:{$outTradeNo}_增加席位失败");
                }
            }

            Db::commit();
            return true;
        }catch (\Throwable $e){
            Db::rollBack();
            //todo 需要增加退款机制
            logger()->info('购买席位：'.$e->getMessage());
            return false;
        }
    }

    public function renewalSeatNotify($outTradeNo,$transactionId,$ext):bool
    {
        $order = $this->orderMapper->findByOrderNo($outTradeNo);

        if(empty($order) || $order->status != 0){
            return false;
        }

        !empty($transactionId) && $order->transaction_id = $transactionId;
        $order->pay_time = time();
        $order->status = 1;

        Db::beginTransaction();
        try {
            if($order->seat_type == 1){
                $rst = $this->licenseService->createRenewOrder($order,$transactionId);
                if(!$rst){
                    throw new NormalStatusException("续费互通账号接口许可证失败");
                }
            }else{
                $order->status = 1;
                $order->transaction_id = $transactionId;
                $order->pay_time = Carbon::now()->toDateTimeString();
                $result = $order->save();
                if(!$result){
                    throw new NormalStatusException("订单编号:{$outTradeNo}_状态更新失败");
                }
                $seatInfo = $this->mapper->findById($order->business_id);
                if($seatInfo->expire_time < time()){
                    $expire_time = Carbon::now()->addMonths($order->month)->timestamp;
                }else{
                    $expire_time = Carbon::parse($seatInfo->expire_time)->addMonths($order->month)->timestamp;
                }

                //变更席位到期时间
                $this->mapper->update($order->business_id,[
                    'expire_time' => $expire_time,
                    'status' => 1
                ]);
            }
            Db::commit();
            return true;
        }catch (\Throwable $e){
            Db::rollBack();
            logger()->info('购买席位：'.$e->getMessage());
            return false;
        }
    }

    /**
     * 增加席位
     * @param int $uid
     * @param int $seatNum
     * @param int $accountType 0=个微 1=企微
     * @param int $seatType 0=基础账号 1=互通账号
     * @param int $month
     * @return bool
     */
    #[Transaction]
    public function addSeat(int $uid,int $seatNum,int $accountType,int $seatType,int $month= 1): bool
    {
        $data = [];
        $expireTime = Carbon::now()->addMonths($month)->timestamp;
        for ($i = 0;$i < $seatNum; $i++){
            $data[] = [
                'uid' => $uid,
                'main_uid' => $uid,
                'code' => Str::getUUID(),
                'expire_time' => $expireTime,
                'type' => $accountType,
                'seat_type' => $seatType
            ];
        }
        //增加用户席位数量
        $this->userMapper->inc($uid,$seatNum,$seatType);
        return $this->batchSave($data);
    }

    /**
     * 获取月份
     * @param int $type
     * @return float|int|string
     */
    public function getMontyByType(int $type): float|int|string
    {
       return match ($type){
            1 => 6,//半年
            2 => 12,//一年
            default => 1//月
        };
    }


    /**
     * 购买记录
     * @param array $params
     * @return array
     */
    public function record(array $params): array
    {
        $pageSize = $params['page_size'] ?? MineModel::PAGE_SIZE;

        $seatType = data_get($params,'seat_type');
        $type = data_get($params,'type');
        $startTime = data_get($params,'start_time');
        $endTime = data_get($params,'end_time');

        $paginate = $this->orderMapper->model::query()
            ->select(['id','order_no','num','month','pay_amount','type','seat_type','status','created_at'])
            ->where('uid',$params['uid'])
            ->when(filled($type),function ($query) use ($type){
                $query->where('type',$type);
            })
            ->when(filled($seatType),function ($query) use ($seatType){
                $query->where('seat_type',$seatType);
            })
            ->when(!empty($startTime) && !empty($endTime),function ($query) use ($startTime,$endTime){
                $query->whereBetween('created_at',[$startTime,$endTime]);
            })
            ->orderBy('created_at','desc')
            ->paginate((int)$pageSize);

        $paginate = $this->orderMapper->setPaginate($paginate);

        $items = $paginate['items'];

        foreach ($items as $k=>$item){
            $items[$k]['type_text'] = $item['type'] == 1 ? '续费' : '购买';
            if($item['status'] == 1){
                $items[$k]['status_text'] = $item['type'] == 0 ? '购买成功' : '续费成功';
            }else{
                $items[$k]['status_text'] = SeatOrderStatusConstants::getMessage($item['status']);
            }

            $month = $item['month'];
            $items[$k]['month_text'] = match ($month){
                12 => '一年',
                6 => '半年',
                default => $month.'个月'
            };

        }
        $paginate['items'] = $items;
        return $paginate;
    }


    public function getSeatList($data): array
    {
        $corpId = data_get($data, 'corp_id');
        if(empty($corpId)){
            $id = $data['id'];

            $robotInfo = $this->robotMapper->findById($id);
            $corpId = $robotInfo->corp_id;
        }

        $status = data_get($data, 'status');
        $pageSize = data_get($data, 'page_size', MineModel::PAGE_SIZE);

        $data = ChSeat::query()
            ->with(['htRobot'])
            ->where('main_uid',$data['main_uid'])
            ->when(!empty($corpId),fn($q) => $q->where('corp_id',$corpId))
            ->when(filled($status),fn($q) => $q->where('use_status',$status))
            ->where('seat_type',1)
            ->where('expire_time','>=',time())
            ->paginate((int)$pageSize);

        $data->getCollection()->transform(function ($item){
           $item->use_status_text = $item->use_status == 0 ? '未绑定' : ($item->htRobot->nickname ?? '已绑定') ;
           unset($item->htRobot);
           return $item;
        });

        return $this->mapper->setPaginate($data);
    }

    #[Transaction]
    public function unbind(array $params)
    {
        $seatInfo = $this->mapper->findById($params['id']);
        if(empty($seatInfo) || $seatInfo->use_status != 1){
           return false;
        }

        $seatInfo->use_status = 0;
        $rst = $seatInfo->save();

        if(!$rst){
            throw new NormalStatusException('解绑失败');
        }

        $robotInfo =ChRobot::query()
            ->where('seat_id',$seatInfo->id)
            ->first();

        if(!empty($robotInfo)){
            if($robotInfo->login_status == 1){
                $apiService = make(Robot::class,[$robotInfo->server_id,$robotInfo->uuid])->gateway();
                $response = $apiService->loginOut();
                if($response === false){
                    throw new NormalStatusException('解绑失败');
                }
            }
            $robotInfo->login_status = 0;
            $robotInfo->seat_id = 0;
            $robotInfo->save();
            container()->get(ChRobotMapper::class)->clearCache($robotInfo->id);
        }

        return true;
    }
}